from typing import Any, Callable, Dict, Generic, List, Optional, TypeVar, Union, cast
from ex4nicegui import ref_computed, to_ref, effect
from ex4nicegui.utils.signals import ReadonlyRef
from nicegui import ui
from weakref import WeakKeyDictionary
from functools import partial


class RefWrapper:
    def __init__(self, value) -> None:
        self._ref = to_ref(value)

    def __getattr__(self, name):
        return getattr(self._ref.value, name)

    def __mod__(self, other):
        return self._ref.value.__mod__(other)

    def __eq__(self, other):
        return self._ref.value.__eq__(other)

    def __ne__(self, other):
        return self._ref.value.__ne__(other)

    def __lt__(self, other):
        return self._ref.value.__lt__(other)

    def __gt__(self, other):
        return self._ref.value.__gt__(other)

    def __le__(self, other):
        return self._ref.value.__le__(other)

    def __ge__(self, other):
        return self._ref.value.__ge__(other)

    def __pos__(self):
        return self._ref.value.__pos__()

    def __neg__(self):
        return self._ref.value.__neg__()

    def __abs__(self):
        return self._ref.value.__abs__()

    def __invert__(self):
        return self._ref.value.__invert__()

    def __round__(self, n):
        return self._ref.value.__round__(n)

    def __floor__(self):
        return self._ref.value.__floor__()

    def __ceil__(self):
        return self._ref.value.__ceil__()

    def __trunc__(self):
        return self._ref.value.__trunc__()

    def __str__(self) -> str:
        return self._ref.value.__str__()

    def __len__(self):
        return self._ref.value.__len__()


class RefDescriptor:
    def __init__(self, value) -> None:
        self.__ref_wrapper = RefWrapper(value)

    def __get__(self, obj, objtype=None):
        return self.__ref_wrapper

    def __set__(self, obj, value):
        self.__ref_wrapper._ref.value = value


class RefState:
    def __init_subclass__(cls) -> None:
        for attr, value in RefState.all(cls):
            setattr(cls, attr, RefDescriptor(value))

    @staticmethod
    def all(child):
        return [
            (name, value)
            for name, value in vars(child).items()
            if name[0] != "_" and not isinstance(value, (Callable, var, property))
        ]


_T_var_get_return = TypeVar("_T_var_get_return")


class var(Generic[_T_var_get_return]):
    __isabstractmethod__: bool

    def __init__(
        self,
        fget: Callable[[Any], _T_var_get_return],
    ) -> None:
        self._fget = fget
        self.__instance_map: WeakKeyDictionary[
            object, ReadonlyRef
        ] = WeakKeyDictionary()

    def __get_computed(self, instance):
        if instance not in self.__instance_map:
            cp = ref_computed(partial(self._fget, instance))
            self.__instance_map[instance] = cp

        return self.__instance_map[instance]

    def __get__(self, __instance: Any, __owner: type | None = None) -> Any:
        return cast(ReadonlyRef[_T_var_get_return], self.__get_computed(__instance))


def try_convert(value, effect_fn: Optional[Callable[[ReadonlyRef], None]] = None):
    if isinstance(value, ReadonlyRef):
        if effect_fn:

            @effect
            def _():
                effect_fn(value)

        return value.value

    if isinstance(value, RefWrapper):
        if effect_fn:

            @effect
            def _():
                effect_fn(value._ref)

        return value._ref.value

    return value


def build_props_bind_fn(element: ui.element, name: str):
    def bind_fn(cp: ReadonlyRef[str]):
        element._props[name] = cp.value

    return bind_fn


def try_bind_and_convert(
    element: ui.element,
    kws: Dict[str, Any],
    *,
    props: Union[List, Dict, None] = None,
    detail: Optional[Dict] = None,
):
    result = kws.copy()
    if props:
        if isinstance(props, list):
            props = {p: build_props_bind_fn(element, p) for p in props}

        for name, fn in props.items():
            org_value = kws[name]
            real_value = try_convert(org_value, fn)
            result[name] = real_value

    if detail:
        for name, fn in detail.items():
            org_value = kws[name]
            real_value = try_convert(org_value, fn)
            result[name] = real_value

    return result


class BinderUI:
    def __init__(self, element: ui.element) -> None:
        self.__element = element
        pass

    def bind_style(self, name: str, value: Any):
        def bind_style(cp: ReadonlyRef[str]):
            self.__element._style[name] = cp.value
            self.__element.update()

        value = try_convert(value, bind_style)

        self.__element._style[name] = value
        self.__element.update()

    def bind_props(self, name: str, value: Any):
        def bind_style(cp: ReadonlyRef[str]):
            self.__element._props[name] = cp.value
            self.__element.update()

        value = try_convert(value, bind_style)

        self.__element._props[name] = value
        self.__element.update()


class RxInput(ui.input):
    def __init__(
        self,
        label: str | None = None,
        *,
        placeholder: str | None = None,
        value: str = "",
        password: bool = False,
        password_toggle_button: bool = False,
        on_change: Callable[..., Any] | None = None,
        autocomplete: List[str] | None = None,
        validation: Dict[str, Callable[..., bool]] | None = None,
    ) -> None:
        kws = {
            name: value
            for name, value in locals().items()
            if name not in ["self", "__class__"]
        }

        def bind_value(cp: ReadonlyRef[str]):
            self.set_value(cp.value)

        value_kws = try_bind_and_convert(
            self, kws, props=["label", "placeholder"], detail={"value": bind_value}
        )

        # value_kws.update({"element": self})

        super().__init__(**value_kws)
        self.__binderUI = BinderUI(self)

        if isinstance(value, RefWrapper):

            def onModelValueChanged(e):
                value._ref.value = e.args or ""  # type: ignore

            self.on("update:modelValue", handler=onModelValueChanged)

    def bind_style(self, name: str, value: Any):
        return self.__binderUI.bind_style(name, value)

    def bind_props(self, name: str, value: Any):
        return self.__binderUI.bind_props(name, value)


class RxLabel(ui.label):
    def __init__(self, text: str = "") -> None:
        def bind_text(cp: ReadonlyRef[str]):
            self.set_text(str(cp.value))

        text = try_convert(text, bind_text)
        super().__init__(text)

        self.__binderUI = BinderUI(self)

    def bind_style(self, name: str, value: Any):
        return self.__binderUI.bind_style(name, value)

    def bind_props(self, name: str, value: Any):
        return self.__binderUI.bind_props(name, value)
